using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using RayDen.Library.Components.SystemComponents;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Threads;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Data;
using RayDen.RayEngine.Samplers;

namespace RayDen.RayEngine
{
    public abstract class DeviceRenderThreadBase : RenderThread
    {

        protected IIntersectionEngine intersectionDevice;
        protected PathBufferBase[] _pathRayProcessors;
        protected RayBuffer[] rayBuffers;
        protected SampleBuffer sampleBuffer;
        protected IImageFilm pixelDevice;
        protected Task renderThread;

        protected int width, height;

        protected DeviceRenderThreadBase(int index, RayEngineScene scene) : base(index, scene) { }
        protected DeviceRenderThreadBase(int index,
                                         IIntersectionEngine device,
                                         RayEngineScene scn,
                                         bool lowLatency,
                                         IImageFilm pixelDevice,
                                         ISampler sampler, SurfaceSampler ss)
            : base(index, scn)
        {
            this.Init(device, lowLatency, pixelDevice, sampler, ss);
        }

        protected void Init(IIntersectionEngine device, bool lowLatency, IImageFilm pixelDevice, ISampler sampler,
                            SurfaceSampler ss)
        {
            this.intersectionDevice = device;
            // Sample buffer
            var sampleBufferSize = lowLatency ? (this.SAMPLE_BUFFER_SIZE / 4) : this.SAMPLE_BUFFER_SIZE;
            this.sampleBuffer = new SampleBuffer(sampleBufferSize);

            this.width = sampler.Width;
            this.height = sampler.Height;
            var rayBufferSize = lowLatency ? (RayBuffer.RayBufferSize / 8) : RayBuffer.RayBufferSize;
            this.sampler = sampler;
            this._pathRayProcessors = new PathBufferBase[DeviceRenderBufferCount];
            this.rayBuffers = new RayBuffer[DeviceRenderBufferCount];
            this.pixelDevice = pixelDevice;
            for (int i = 0; i < DeviceRenderBufferCount; i++)
            {
                this.rayBuffers[i] = new RayBuffer(rayBufferSize);
                this.rayBuffers[i].UserData = this.threadIndex;

                this._pathRayProcessors[i] = this.CreatePathBuffer(this.scene.MaxPaths, this.scene, pixelDevice, sampler, ss);
                this._pathRayProcessors[i].ThreadId = this.threadIndex;
            }

            this.renderThread = null;
        }

        private CancellationTokenSource cancelToken;


        protected abstract PathBufferBase CreatePathBuffer(int maxPath, IRayEngineScene scene, IImageFilm pixelDevice,
                                                           ISampler sampler, SurfaceSampler ss);

        public override void Start()
        {
            base.Start();

            this.sampler.Init(this.width, this.height);

            this.sampleBuffer.Reset();
            for (int i = 0; i < DeviceRenderBufferCount; i++)
            {
                this.rayBuffers[i].Reset();
                this.rayBuffers[i].ResetUserData();
                this.rayBuffers[i].UserData = (i);
                this._pathRayProcessors[i].ReInit();
            }
            this.cancelToken = new CancellationTokenSource();
            // Create the thread for the rendering
            this.renderThread = Task.Factory.StartNew(() => RenderThreadImpl(this.cancelToken.Token, this), this.cancelToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
            this.intersectionDevice.Start();
        }
        public override void Interrupt()
        {
            if (this.renderThread.Status == TaskStatus.Running)
            {
                this.cancelToken.Cancel();
            }
        }
        public override void Stop()
        {
            try
            {
                if (this.renderThread.Status == TaskStatus.Running)
                {
                    this.cancelToken.Cancel();
                }
                while (this.renderThread.Status == TaskStatus.Running)
                {

                }
            }
            finally
            {
                this.renderThread.Dispose();
                this.renderThread = null;
                base.Stop();
            }
        }
        public override void ClearPaths()
        {
            for (int i = 0; i < DeviceRenderBufferCount; i++)
            {
                this._pathRayProcessors[i].ClearPaths();
            }
        }

        public override int GetPass()
        {
            return this.sampler.GetPass();
        }

        protected virtual void ThreadJob(CancellationToken t, DeviceRenderThreadBase renderThread)
        {
            Tracer.TraceLine("CL Render Thread {0} started", renderThread.threadIndex);

#if VERBOSE
            try {
#endif
            var todoBuffers = new Queue<RayBuffer>();
            for (var i = 0; i < DeviceRenderBufferCount; i++)
                todoBuffers.Enqueue(renderThread.rayBuffers[i]);

            while (!t.IsCancellationRequested)
            {
                while (todoBuffers.Count > 0)
                {
                    var rayBuffer = todoBuffers.Dequeue();
                    rayBuffer.Reset();
                    renderThread._pathRayProcessors[0].FillRayBuffer(rayBuffer);
                    renderThread.intersectionDevice.PushRayBuffer(rayBuffer, renderThread.threadIndex);
                }
                var rayBuff = renderThread.intersectionDevice.PopRayBuffer(renderThread.threadIndex);
                if (rayBuff != null && rayBuff.UserData == renderThread.threadIndex)
                {
                    renderThread._pathRayProcessors[0].AdvancePaths(rayBuff);
                    todoBuffers.Enqueue(rayBuff);
                }
            }
#if VERBOSE
            }
            catch (Exception ex) {
                Tracer.TraceLine("Exiting rendering : Reason \n\r{0}", ex);
            }
#endif
            Tracer.TraceLine("CL Render Thread {0} exited", renderThread.threadIndex);
        }


        protected static void RenderThreadImpl(CancellationToken t, DeviceRenderThreadBase renderThread)
        {
            renderThread.ThreadJob(t, renderThread);
        }
    }
}